package com.solution.util.scheduler;

import java.text.ParseException;
import java.util.List;

import org.apache.log4j.Logger;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

import com.solution.constant.SchedulerConstants;
import com.solution.delegate.SchdJobAssociationsServiceDelegate;
import com.solution.exception.DelegateException;
import com.solution.exception.RepositoryNotInitializedException;
import com.solution.exception.SchedulerNotInitializedException;
import com.solution.exception.ServiceBeanException;
import com.solution.model.SchdCronTriggers;
import com.solution.model.SchdJobAssociations;
import com.solution.model.SchdJobDetails;
import com.solution.util.Repository;

public class SchedulerService {
	private static Logger log = Logger.getLogger(SchedulerService.class
			.getName());
	/**
	 * DAO instance.
	 */
	private static SchdJobAssociationsServiceDelegate schdJobAssociationsServiceDelegate = null;

	private static SchedulerService service = null;
	private StdSchedulerFactory stdSchedulerFactory = null;
	private Scheduler scheduler = null;

	/**
	 * Constructor <code>SchedulerService.java</code>
	 */
	private SchedulerService() {
		super();
		try {
			schdJobAssociationsServiceDelegate = new SchdJobAssociationsServiceDelegate();
		} catch (DelegateException e) {
			log.error(e, e);
			throw new SchedulerNotInitializedException(e);
		}
		initService();
	}

	/**
	 * Method <code>getService</code>
	 * <p>
	 * 
	 * @return <p>
	 */
	public static final SchedulerService getService() {
		if (service == null) {
			service = new SchedulerService();
		}
		return service;
	}

	/**
	 * Method <code>getService</code>
	 * <p>
	 * 
	 * @return <p>
	 */
	public static final SchedulerService getInitializedService() {
		return service;
	}

	/**
	 * Method <code>initService</code>
	 * <p>
	 * 
	 * @throws SchedulerNotInitializedException
	 *             <p>
	 */
	public void initService() throws SchedulerNotInitializedException {
		try {
			log.debug("initialize scheduler");
			// initialize scheduler
			stdSchedulerFactory = new StdSchedulerFactory(Repository
					.getRepository().getQuartzProperty());
			scheduler = stdSchedulerFactory.getScheduler();
			scheduler.start();

			List<SchdJobAssociations> schdJobAssociationsList = schdJobAssociationsServiceDelegate
					.getActiveJobAssosciations();

			for (SchdJobAssociations schdJobAssociations : schdJobAssociationsList) {
				scheduleJob(schdJobAssociations.getSchdJobDetails(),
						schdJobAssociations.getSchdCronTriggers());
			}
		} catch (SchedulerException e) {
			log.error(e, e);
			throw new SchedulerNotInitializedException(e);
		} catch (RepositoryNotInitializedException e) {
			log.error(e, e);
			throw new SchedulerNotInitializedException(e);
		} catch (ServiceBeanException e) {
			log.error(e, e);
			throw new SchedulerNotInitializedException(e);
		}
	}

	/**
	 * Method <code>stopService</code>
	 * <p>
	 * 
	 * @throws SchedulerNotInitializedException
	 *             <p>
	 */
	public void stopService() throws SchedulerNotInitializedException {
		if (scheduler != null) {
			try {
				scheduler.shutdown();
				log.info("Shut down quartz scheduler");
			} catch (SchedulerException se) {
				log.error(se, se);
			}
		}
	}

	public void scheduleJob(SchdJobDetails schdJobDetails,
			SchdCronTriggers schdCronTriggers) throws SchedulerException {

		Long jobDetailId = null;
		Long cronTriggerId = null;
		String jobName;
		String jobGroup;
		String jobClass;
		boolean isJobVolatile;
		boolean isJobDurable;
		boolean requestRecovery;
		String triggerName;
		String triggerGroup;
		String cronExpression;

		CronTrigger cronTrigger = null;
		JobDetail jobDetail = null;
		Trigger trigger = null;

		try {
			if (schdJobDetails != null && schdCronTriggers != null) {
				jobDetailId = schdJobDetails.getId();
				jobName = schdJobDetails.getJobName();
				jobGroup = schdJobDetails.getJobGroup();
				jobClass = schdJobDetails.getJobClassName();
				isJobVolatile = schdJobDetails.getIsVolatile();
				isJobDurable = schdJobDetails.getIsDurable();
				requestRecovery = schdJobDetails.getRequestsRecovery();
				cronTriggerId = schdCronTriggers.getId();
				triggerName = jobName + "|" + schdCronTriggers.getTriggerName();
				triggerGroup = schdCronTriggers.getTriggerGroup();
				cronExpression = schdCronTriggers.getCronExpression();
			} else {
				log
						.debug("Atleast one of Job and Trigger are null and could not be retrieved");
				return;
			}

			// get the job details and trigger if they are already existing in
			// scheduler
			jobDetail = scheduler.getJobDetail(jobName, jobGroup);
			trigger = scheduler.getTrigger(triggerName, triggerGroup);

			if (jobDetail == null) {
				// check if this trigger is already running on this job.
				if (trigger == null) {

					// create a new instance of jobDetail
					jobDetail = new JobDetail(jobName, jobGroup, Class
							.forName(jobClass), isJobVolatile, isJobDurable,
							requestRecovery);

					// create a new instance of cronTrigger
					cronTrigger = new CronTrigger(triggerName, triggerGroup,
							cronExpression);
					cronTrigger.setVolatility(isJobVolatile);

					try {
						if (cronTriggerId == SchedulerConstants.STOP_TRIGGER_ID) {
							// instruction to stop the trigger used in
							// replaceJobAssociationID
							log
									.debug("JobDetail is not added into scheduler. Instruction to stop trigger on such job is not valid.");
						} else if (cronTriggerId == SchedulerConstants.TRIGGER_NOW_ID) {
							// instruction to fire the job right now
							// add job to scheduler
							scheduler.addJob(jobDetail, true);
							if (isJobVolatile) {
								scheduler.triggerJobWithVolatileTrigger(
										jobName, jobGroup);
							} else {
								scheduler.triggerJob(jobName, jobGroup);
							}
							log
									.info("Job with JobDetailId:"
											+ jobDetailId
											+ " is added to the scheduler and is fired");
						} else {
							// instruction to schedule job with cronTrigger. May
							// have replaceJobAssociationID to stop the trigger
							// Assuming scheduling job for the first time
							scheduler.scheduleJob(jobDetail, cronTrigger);
							log
									.info("Job with JobDetailId:"
											+ jobDetailId
											+ " is added to the scheduler and is scheduled to be fired with CronTriggerId:"
											+ cronTriggerId);
						}
					} catch (SchedulerException se) {
						log.error(se,se);
						// if there is already a trigger for the job then create
						// new trigger on the job
						log
								.debug("There is an error in scheduling the job. Job is null.");
					}
				} else {
					log
							.debug("Trigger is already running on job. Job is null but not trigger.");
				}

			} else {
				// check if this cron trigger is already running on this job.
				if (trigger == null) {

					// create a new instance of cronTrigger
					cronTrigger = new CronTrigger(triggerName, triggerGroup,
							jobName, jobGroup, cronExpression);
					cronTrigger.setVolatility(isJobVolatile);
					try {
						if (cronTriggerId == SchedulerConstants.STOP_TRIGGER_ID) { // instruction
							// to
							// stop
							// the
							// trigger
							// used
							// in
							// replaceJobAssociationID
							log
									.debug("Job is already in database. Instruction to remove trigger but no trigger identified");
						} else if (cronTriggerId == SchedulerConstants.TRIGGER_NOW_ID) { // instruction
							// to
							// fire
							// the
							// job
							// right
							// now
							if (isJobVolatile) {
								scheduler.triggerJobWithVolatileTrigger(
										jobName, jobGroup);
							} else {
								scheduler.triggerJob(jobName, jobGroup);
							}
							log
									.info("Job with JobDetailId:"
											+ jobDetailId
											+ " is added to the scheduler and is fired");
						} else { // instruction to schedule job with
							// cronTrigger. May have
							// replaceJobAssociationID to stop the
							// trigger
							scheduler.scheduleJob(cronTrigger);
							log
									.debug("Job with JobDetailId:"
											+ jobDetailId
											+ " is added to the scheduler and is scheduled to be fired with CronTriggerId:"
											+ cronTriggerId);
						}
					} catch (SchedulerException se) {
						log.error(se,se);
						// if there is already a trigger for the job then create
						// new trigger on the job
						log
								.debug("There is an error in scheduling the job. Job is not null.");
					}
				} else {
					log
							.debug("Trigger is already running on job. Job and trigger are not null.");
				}
			}
		} catch (ParseException pe) {
			log.error("Invalid Cron Expression for CronTriggerId:"
					+ cronTriggerId);
		} catch (ClassNotFoundException cnfe) {
			log.error("Job Class Not Found for JobDetailId:" + jobDetailId);
		}
	}

	public void scheduleEventJob(JobDetail jobDetail, SimpleTrigger simpleTrigger) throws SchedulerException {
		scheduler.scheduleJob(jobDetail, simpleTrigger);
	}
}
